///
/// AEye, Inc. ("COMPANY") CONFIDENTIAL
/// Unpublished Copyright (c) 2013-2023 AEye, Inc., All Rights Reserved.
///
/// NOTICE:  All information contained herein is, and remains the property of COMPANY.
/// The intellectual and technical concepts contained herein are proprietary to COMPANY and
/// may be covered by U.S. and Foreign Patents, patents in process, and are protected by trade secret or copyright law.
/// Dissemination of this information or reproduction of this material is strictly forbidden unless
/// prior written permission is obtained from COMPANY.
/// Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, managers,
/// or contractors who have executed Confidentiality and Non-disclosure agreements explicitly covering such access.
///
/// The copyright notice above does not evidence any actual or intended publication or disclosure of this source code,
/// which includes information that is confidential and/or proprietary, and is a trade secret, of  COMPANY.
/// ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS
/// SOURCE CODE WITHOUT THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE
/// LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION DOES NOT
/// CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE, OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, USE, OR SELL
/// ANYTHING THAT IT  MAY DESCRIBE, IN WHOLE OR IN PART.
///

/// @file Sensor.hpp
/// @brief High-level interface to AEye's 4SightM sensor.

#ifndef SENSOR_HPP
#define SENSOR_HPP

#include <cstddef>
#include <cstdint>
#include <functional>
#include <iosfwd>
#include <memory>
#include <string>
#include <vector>

#include "AEyeAPIConfig.hpp"
#include "AEyeSDKStructures.hpp"
#include "DiagnosticAPI.hpp"
#include "PcapPacket.hpp"

#ifdef _MSC_VER
#include <initializer_list>
#endif
class SensorReader;

namespace aeye {
  class PointCloudFrame;
  class SensorCommander;
}

namespace aeye {

  enum class UdpBroadcastMode : bool {
    DISABLED,
    ENABlED,
  };
  struct SensorConfig {
    uint16_t diagnostic_port = DEFAULT_DIAGNOSTIC_PORT;
    UdpBroadcastMode broadcast_mode = UdpBroadcastMode::DISABLED;
  };
  struct DataPortConfig {
    uint16_t points = DEFAULT_CLIENT_POINTS_PORT;
    uint16_t diag = DEFAULT_DIAGNOSTIC_PORT;
  };


  /// @class Sensor
  /// @brief High-level interface to AEye's 4SightM sensor
#ifdef AEYE_SDK_DEV
  class Sensor {
#else
  class Sensor final {
#endif
  public:
    /// @cond UNSUPPORTED

    /**
   * @brief Data type to describe size, with fields width and height
   */
    struct Size {
      size_t width;
      size_t height;
    };
    /// @endcond

    /// @brief Constructor for Sensor object. Takes desired network settings as arguments.
    AEYE_DEPRECATED
    Sensor(const std::string& netInterface, const std::string& ipAddress, const std::string& name,
           uint16_t commandPort = DEFAULT_COMMAND_PORT, uint16_t clientPointsPort = DEFAULT_CLIENT_POINTS_PORT,
           uint16_t clientImagePort = DEFAULT_CLIENT_IMAGE_PORT, uint16_t serverPointsPort = DEFAULT_SERVER_POINTS_PORT,
           uint16_t serverImagePort = DEFAULT_SERVER_IMAGE_PORT, uint16_t clientDiagPort = DEFAULT_DIAGNOSTIC_PORT,
           bool udpBroadcastMode = false);

    /// @brief Constructor for Sensor object. Configuration objects can be constructed with default values and
    /// overwritten with desired configuration changes.
    ///
    /// @param sensor_config Configuration object for miscellaneous sensor options.
    /// @param data_ports Configuration object for the client server and image ports.
    /// @param net_interface The network interface the sensor is on.
    /// @param sensor_ip The IP address of the sensor to connect to.
    /// @param sensor_name A convenient name to use to refer to the sensor.
    Sensor(SensorConfig sensor_config, DataPortConfig data_ports,
           std::string net_interface, std::string sensor_ip, std::string sensor_name);

    /// @brief Destructor for Sensor object. Tells the real-world sensor to stop().
    virtual ~Sensor();

    /// @brief Set handler for incoming point cloud data.
    /// @param processPointCloudCallback Functor designated for point cloud processing.
    void setOnPointCloudFrameCallback(const std::function<void(PointCloudFrame&&)>& processPointCloudCallback);

    /// @cond UNSUPPORTED
    /// @brief Set handler for incoming diagnostics data.
    /// @param processDiagnosticCallback Functor designated for diagnostics data processing.
    void setOnDiagnosticDataCallback(const std::function<void(DiagnosticData&&)>& processDiagnosticCallback);
    /// @endcond

    /// @brief Connect to sensor and update Sensor object's member variables to match sensor state.
    void connect();

    /// @brief After connect() has been called, this tells the sensor to start scanning.
    virtual void start();

    /// @cond UNSUPPORTED
    /// @brief After connect() has been called, this tells the sensor to start running diagnostics.
    void startDiagnostic();
    /// @endcond

    /// @brief Disconnect from sensor. Tells the sensor to stop() beforehand.
    void disconnect();

    /// @brief Tells the sensor to stop and the SDK to stop processing data.
    ///        The sensor will remain available for client connection.
    void stop();

    /// @cond UNSUPPORTED
    /// @brief Reboot the connected sensor.
    ///        Note that this will first stop the sensor service, then shut down the sensor, disconnecting it.
    void reboot();
    /// @endcond

    /// @brief Reset the internal state of data-collecting processes.
    ///        This is necessary if looping recorded PCAP data, to restart from the beginning of a stream.
    void resetPlaybackState();

    /// @brief Get the index of the currently firing shotlist from the sensor.
    /// @return Index of the shot list. -1 if error or not in static shot list mode.
    int8_t getCurrentShotlistIndex();

    /// @brief Refresh local cache of sensor internal state.
    /// @return True if the sensor's internal state was successfully updated, false if any one portion failed.
    bool collectSensorState();

    /// @brief Synchronizes sensor time with client time.
    void sendTime();

    /// @brief Updates the sensor's configurable network interface. Takes effect on sensor reboot.
    /// @param server_ip Sets sensor IP address.
    /// @param netmask Sets sensor netmask.
    /// @param gateway Sets sensor gateway.
    void sendNetworkConfig(const std::string& server_ip, const std::string& netmask,
                           const std::string& gateway);

    /// @brief Gets the state of the sensor's configurable network interface.
    /// @param server_ip The sensor IP address.
    /// @param netmask The sensor netmask.
    /// @param gateway The sensor gateway.
    /// @exception aeye::return_status_error<ReturnStatus::BAD_PARAMETER> if invalid server_ip, netmask or gateway are
    ///            received from sensor
    void getNetworkConfig(std::string& server_ip, std::string& netmask,
                           std::string& gateway);

    /// @cond UNSUPPORTED
    /// @brief Configure NTP server settings
    /// @param enabled_ntp enable or disable NTP
    /// @param ntp_ip desired NTP server's IP address
    void sendNtpServer(bool enabled_ntp, const std::string& ntp_ip);
    /// @endcond

    /// @brief Enable the PTP daemon on the sensor
    /// @param enabled_ptp enable or disable PTP
    void sendPtpServer(bool enabled_ptp);

    /// @brief Enable the gPTP daemon on the sensor
    /// @param enabled_gptp enable or disable gPTP
    void sendGptpServer(bool enabled_gptp);

    /// @brief Set what data will be returned in the point cloud data stream.
    /// @param pointReturnMask Bitmask of available data types.
    /// @sa enum PointReturnFlags
    void sendPointReturnMask(uint16_t pointReturnMask);

    /// @brief Set the parameter whose data will be returned as PointReturnIntensity.intensity_type_data.
    /// @param intensityType Identifier of requested intensity type.
    /// @sa enum IntensityTypes
    void sendIntensityType(uint8_t intensityType);

    /**
     * @brief Command sensor to perform sensing modes flagged by \p passiveModeMask.
     * Note that currently the only valid masks are PassiveModeFlags::{SHOTLIST_FROM_FILE, DISABLED}
     * @param passiveModeMask Bitmask indicating which passive modes to enable.
     */
    void sendPassiveMode(uint8_t passiveModeMask);

    /// @brief Set which embedded shotlist file to use.
    ///        NOTE: N is the number of shotlists on your sensor, which can be obtained by calling the
    ///              Sensor::getScanPatternFileNames(), and getting the length of the returned vector.
    /// @param value index of your desired shot list {from 0 to N-1}
    void sendShotlistFileNumber(int value);

    /// @brief Selects a subset of 4 multi echo returns to be streamed.
    /// @param whichEchoes Bit mask whose lower 4 bits determine which echo to enable (0th bit is 0th echo, etc.).
    ///                    Upper 4 bits are unused.
    /// @sa enum PointRxType
    void sendNumMultiEchoes(uint8_t whichEchoes);

    /// @brief get number of echoes that are being streamed from sensor
    /// @return num of echoes
    AEYE_NODISCARD
    uint8_t getNumMultiEchoes();

    /// @brief sets multi echo return mode
    void setModeMultiEchoes(MultiEchoMode mode);

    /// @brief get current multi echo mode
    AEYE_NODISCARD
    MultiEchoMode getModeMultiEchoes();

    /// @brief set threshold for near field high gain
    void setSkirtThreshNearHigh(uint16_t value);

    /// @brief set threshold for near field low gain
    void setSkirtThreshNearLow(uint16_t value);

    /// @brief set threshold for far field high gain
    void setSkirtThreshFarHigh(uint16_t value);

    /// @brief set threshold for far field low gain
    void setSkirtThreshFarLow(uint16_t value);

    /// @brief get threshold for near field high gain
    AEYE_NODISCARD
    uint16_t getSkirtThreshNearHigh();

    /// @brief get threshold for near field low gain
    AEYE_NODISCARD
    uint16_t getSkirtThreshNearLow();

    /// @brief get threshold for far field high gain
    AEYE_NODISCARD
    uint16_t getSkirtThreshFarHigh();

    /// @brief get threshold for far field low gain
    AEYE_NODISCARD
    uint16_t getSkirtThreshFarLow();

    /// @brief get shot list name that corresponds to index i
    AEYE_NODISCARD
    std::string getShotlistName(uint16_t i);

    /// @cond UNSUPPORTED
    /// @brief queries sensor's NTP status
    /// @return 0 - synchronized, 1 - not synchronized, 2 - clock state is indeterminant
    AEYE_NODISCARD
    TimeServerStatus getNtpStatus();
    /// @endcond

    /// @brief queries sensor's NTP status
    /// @return 0 - synchronized, 1 - not synchronized, 2 - clock state is indeterminant
    AEYE_NODISCARD
    TimeServerStatus getPtpStatus();

    /// @brief queries sensor's gPTP status
    /// @return 0 - synchronized, 1 - not synchronized, 2 - clock state is indeterminant
    AEYE_NODISCARD
    /// @details This will also fill in additional gPTP details in #gPtpServerStatusParams, which can then be retrieved
    ///          via #getGptpServerStatusParams();
    TimeServerStatus getGptpStatus();

    /// @brief Set sensor position and orientation.
    /// @param x The axis looking out the barrel of the sensor. (Front)
    /// @param y The axis which is neither x nor z. (Left)
    /// @param z The vertical axis. (Up)
    /// @param yaw Rotation about the z axis, in degrees.
    /// @param pitch Rotation about the y axis, in degrees.
    /// @param roll Rotation about the x axis, in degrees.
    void sendPose(double x, double y, double z, double yaw, double pitch, double roll);

    /// @cond UNSUPPORTED
    struct Quadrilateral {
      uint16_t      topLeftX;
      uint16_t      topLeftY;
      uint16_t      topRightX;
      uint16_t      topRightY;
      uint16_t      btmLeftX;
      uint16_t      btmLeftY;
      uint16_t      btmRightX;
      uint16_t      btmRightY;

      Quadrilateral() = default;

      explicit Quadrilateral(const aeye::Quadrilateral& other) {
        topLeftX = other.topLeftX;
        topLeftY = other.topLeftY;
        topRightX = other.topRightX;
        topRightY = other.topRightY;
        btmLeftX = other.btmLeftX;
        btmLeftY = other.btmLeftY;
        btmRightX = other.btmRightX;
        btmRightY = other.btmRightY;
      };
      Quadrilateral& operator=(const aeye::Quadrilateral& other);
    };
    /// @endcond

    // Getters
    AEYE_NODISCARD
    const std::string& getClientIp()                      const;
    AEYE_NODISCARD
    const std::string& getInterface()                     const;
    AEYE_NODISCARD
    const std::string& getSensorIp()                      const;
    AEYE_NODISCARD
    const std::string& getName()                          const;
    AEYE_NODISCARD
    bool               isConnected()                      const;
    AEYE_NODISCARD
    bool               isSensing()                        const;
    AEYE_NODISCARD
    bool               isPcapMode()                       const;
    AEYE_NODISCARD
    uint16_t           getCommandPort()                   const;
    AEYE_NODISCARD
    uint16_t           getRangePort()                     const;
    AEYE_NODISCARD
    uint16_t           getServerRangePort()               const;
    AEYE_NODISCARD
    const std::string& getBuildInfo()                     const;
    AEYE_NODISCARD
    ModelType          getSensorModel()                   const;
    /// @cond UNSUPPORTED
    uint8_t            getPassiveModeMask()               const;
    /// @endcond
    AEYE_NODISCARD
    uint16_t           getPointReturnMask()               const;
    AEYE_NODISCARD
    uint16_t           getLaserPower()                    const;
    /// @cond UNSUPPORTED
    AEYE_DEPRECATED
    Quadrilateral      getLaserZone()                     const;
    AEYE_DEPRECATED
    uint16_t           getLaserZoneHorFOV()               const;
    AEYE_DEPRECATED
    uint16_t           getLaserZoneVerFOV()               const;
    /// @endcond
    AEYE_NODISCARD
    const Pose&        getPose()                          const;

    /// @brief Get time correctness of NTP sync
    /// @return Difference between clocks server/client or peer/peer
    AEYE_NODISCARD
    float              getTimeCorrectness()               const;

    /// @brief get time constant of NTP sync
    /// @return 2^(timeConstant) == how often polling server or peer's clock
    AEYE_NODISCARD
    float              getTimeConstant()                  const;

    /// @brief Get PTP Timestamp
    /// @return Timestamp recorded according to the PTP daemon
    AEYE_NODISCARD
    std::string        getPTPTimestamp()                  const;

    /// @brief Get PTP State
    /// @return State of the PTP daemon, i.e. slave, master, initializing...
    AEYE_NODISCARD
    std::string        getPTPState()                      const;

    /// @brief Get PTP Clock ID
    /// @return Unique clockId assigned by the PTP daemon
    AEYE_NODISCARD
    std::string        getPTPClockID()                    const;

    /// @brief Get PTP One-Way Delay
    /// @return the delay of a message one way, according to the state of the PTP daemon
    AEYE_NODISCARD
    float              getPTPOneWayDelay()                const;

    /// @brief Get PTP Offset From Master
    /// @return the time offset calculated between the PTP daemon and slave
    AEYE_NODISCARD
    float              getPTPOffsetFromMaster()           const;

    /// @brief Get the delay embedded in the PTP Slave to Master Delay Request Packet
    /// @return Intermediate offset value from the delay request
    AEYE_NODISCARD
    float              getPTPSlaveToMaster()              const;

    /// @brief Get the delay embedded in the PTP Master to Slave Sync Request Packet
    /// @return intermediate offset value from the synq request
    AEYE_NODISCARD
    float              getPTPMasterToSlave()              const;

    /// @brief Get PTP Observed Drift
    /// @return Observer drift between the slave and master
    AEYE_NODISCARD
    float              getPTPObservedDrift()              const;

    /// @brief Get PTP Last Packet Received
    /// @return last packet received, either donates packet from master (synq request) or slave (delay request)
    AEYE_NODISCARD
    std::string        getPTPLastPacketReceived()         const;

    /// @brief Get gPTP Last Packet Received
    /// @return last packet received, either donates packet from master (synq request) or slave (delay request)
    GptpServerStatus   getGptpServerStatusParams()        const;

    /// @brief Switch between Pcap playback and live sensing.
    /// @param value Enable/disable.
    void setPcapMode(bool value);

    /// @brief set sensor name in Sensor object (does not change name of physical sensor)
    void setName(const std::string& name);

    /**
     * @brief Takes in PcapPacket, processes it, and feeds it to the sensorReader.
     * @param packet PcapPacket to be processed.
     */
    void processPcapPacket(PcapPacket& packet);

    /// @brief Print cached sensor state to \p out.
    /// @param out Stream to which to print sensor state.
    void printState(std::ostream& out);

    AEYE_NODISCARD
    const std::vector<std::string>& getScanPatternFileNames();

    /// @brief Toggles TCP Keep Alive
    /// @param enable TCP Keep Alive bool: True = on, False = off
    void SetTCPKeepAlive(bool enable);

    /// @brief Gets the status of TCP Keep Alive
    AEYE_NODISCARD
    bool GetTCPKeepAlive();

  protected:
    std::string                            clientIp;
    std::string                            clientInterface;
    std::string                            sensorIp;
    std::string                            name;
    ModelType                              sensorModelType = ModelType::UNKNOWN;
    uint16_t                               commandPort;
    uint16_t                               rangePort;
    /// @cond UNSUPPORTED
    uint16_t                               diagnosticPort;
    /// @endcond
    uint16_t                               serverRangePort;
    std::unique_ptr<SensorCommander>       sensorCommander;
    std::unique_ptr<SensorReader>          sensorReader;
    bool                                   sensingOn;
    bool                                   pcapModeOn;
    std::string                            buildInfo;

    uint8_t scanPatternFlags{};
    uint32_t scanBlockId{};
    std::string macAddress{};

    // Multi echoes
    uint8_t numEchoes;
    MultiEchoMode modeMultiEchoes;

    /// @cond UNSUPPORTED
    /// @brief Specifies the status and parameters pertaining to a NTP Time Server
    TimeServerStatus NTPstatus; /// 0 - synchronized, 1 - not synchronized, 2 - clock state is indeterminant
    float timeCorrectness; /// difference between clocks server/client or peer/peer
    float timeConstant; /// 2^(timeConstant) == how often polling server or peer's clock
    /// @endcond

    /// @brief Specifies the status and parameters pertaining to a PTP Time Server
    TimeServerStatus PTPstatus; /// 0 - synchronized, 1 - not synchronized, 2 - clock state is indeterminant
    std::string PTPtimeStamp; /// Timestamp recorded according to the PTP daemon
    std::string PTPstate; /// State of the PTP daemon, i.e. slave, master, initializing...
    std::string PTPclockId; /// Unique clockId assigned by the PTP daemon
    float PTPoneWayDelay; /// Delay of a message one way, according to the state of the PTP daemon
    float PTPoffsetFromMaster; /// Time offset calculated between the PTP daemon and slave
    float PTPslaveToMaster; /// Intermediate offset value from the delay request
    float PTPmasterToSlave; /// Intermediate offset value from the synq request
    float PTPobservedDrift; ///  Observer drift between the slave and master
    std::string PTPlastPacketReceived; /// Last packet received, either donates packet from master (synq request) or slave (delay request)

    /// @brief Specifies the status and parameters pertaining to a PTP Time Server
    GptpServerStatus gPtpServerStatusParams;

    float      laserPower;
    /// @cond UNSUPPORTED
    Quadrilateral laserZone;
    float      laserZoneHorFOV;
    float      laserZoneVerFOV;
    /// @endcond

    uint16_t pointReturnMask;

    /// @cond UNSUPPORTED
    uint8_t     passiveModeMask;
    /// @endcond

    Pose pose;

    std::vector<std::string> scanPatternFileNames;

   /// @brief Get local IP address for a given named network adapter.
   /// @param [in/out] interface - The network adapter logical name ('enusb0', 'eth0', etc.).
   ///          If interface equal empty string, will try to find any valid network interface in the system and use it.
   /// @return std::string Interface's public IP address if it exists.
    std::string getLocalIP(std::string& netInterface);
};
}
#endif // SENSOR_HPP
